package controller

import (
	"context"
	"fmt"
	"strings"

	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	utilruntime "k8s.io/apimachinery/pkg/util/runtime"
	coreinformers "k8s.io/client-go/informers/core/v1"
	"k8s.io/client-go/kubernetes"
	corelisters "k8s.io/client-go/listers/core/v1"
	"k8s.io/client-go/tools/cache"
	"k8s.io/client-go/tools/record"
	"k8s.io/client-go/util/retry"
	"k8s.io/klog/v2"
)

// ServiceControlInterface manages Services used in TidbCluster
type EndpointsControlInterface interface {
	Update(runtime.Object, *corev1.Endpoints) (*corev1.Endpoints, error)
}

type realEndpointsControl struct {
	kubeCli  kubernetes.Interface
	epLister corelisters.EndpointsLister
	recorder record.EventRecorder
}

// NewRealEndpoinControl creates a new EndpointControlInterface
func NewRealEndpoinControl(kubeCli kubernetes.Interface, epsLister corelisters.EndpointsLister, recorder record.EventRecorder) EndpointsControlInterface {
	return &realEndpointsControl{
		kubeCli,
		epsLister,
		recorder,
	}
}

func (c *realEndpointsControl) Update(controller runtime.Object, eps *corev1.Endpoints) (*corev1.Endpoints, error) {
	controllerMo, ok := controller.(metav1.Object)
	if !ok {
		return nil, fmt.Errorf("%T is not a metav1.Object, cannot call sepgontrollerReference", controller)
	}
	kind := controller.GetObjectKind().GroupVersionKind().Kind
	name := controllerMo.GetName()
	namespace := controllerMo.GetNamespace()
	epsName := eps.GetName()
	epsSubsets := eps.DeepCopy().Subsets

	var updateEps *corev1.Endpoints
	err := retry.RetryOnConflict(retry.DefaultBackoff, func() error {
		var updateErr error
		updateEps, updateErr = c.kubeCli.CoreV1().Endpoints(namespace).Update(context.TODO(), eps, metav1.UpdateOptions{})
		if updateErr == nil {
			klog.Infof("update Endpoints: [%s/%s] successfully, kind: %s, name: %s", namespace, epsName, kind, name)
			return nil
		}

		if updated, err := c.epLister.Endpoints(namespace).Get(epsName); err != nil {
			utilruntime.HandleError(fmt.Errorf("error getting updated Endpoints %s/%s from lister: %v", namespace, epsName, err))
		} else {
			eps = updated.DeepCopy()
			eps.Subsets = epsSubsets
		}

		return updateErr
	})
	return updateEps, err
}

func (c *realEndpointsControl) recordEndpointsEvent(verb, name, kind string, object runtime.Object, svc *corev1.Service, err error) {
	svcName := svc.GetName()
	if err == nil {
		reason := fmt.Sprintf("Successful%s", strings.Title(verb))
		msg := fmt.Sprintf("%s Endpoint %s in %s %s successful",
			strings.ToLower(verb), svcName, kind, name)
		c.recorder.Event(object, corev1.EventTypeNormal, reason, msg)
	} else {
		reason := fmt.Sprintf("Failed%s", strings.Title(verb))
		msg := fmt.Sprintf("%s Endpoint %s in %s %s failed error: %s",
			strings.ToLower(verb), svcName, kind, name, err)
		c.recorder.Event(object, corev1.EventTypeWarning, reason, msg)
	}
}

var _ EndpointsControlInterface = &realEndpointsControl{}

// FakeEndpointsControl is a fake EndpointsControl
type FakeEndpointsControl struct {
	EpsLister                corelisters.EndpointsLister
	EpsIndexer               cache.Indexer
	createTracker            RequestTracker
	updateTracker            RequestTracker
	deleteStatefulSetTracker RequestTracker
}

// NewFakeEndpointsControl returns a FakeServiceControl
func NewFakeEndpointsControl(svcInformer coreinformers.ServiceInformer, epsInformer coreinformers.EndpointsInformer) *FakeEndpointsControl {
	return &FakeEndpointsControl{
		epsInformer.Lister(),
		epsInformer.Informer().GetIndexer(),
		RequestTracker{},
		RequestTracker{},
		RequestTracker{},
	}
}

// Update updates the service of SvcIndexer
func (c *FakeEndpointsControl) Update(_ runtime.Object, eps *corev1.Endpoints) (*corev1.Endpoints, error) {
	defer c.updateTracker.Inc()
	if c.updateTracker.ErrorReady() {
		defer c.updateTracker.Reset()
		return nil, c.updateTracker.GetError()
	}

	err := c.EpsIndexer.Update(eps)
	if err != nil {
		return nil, err
	}

	return eps, nil
}

var _ ServiceControlInterface = &FakeServiceControl{}
